<?php

class QueryException extends Exception {}
/**
 * Description of Query
 *
 * @author michamaciejewski
 */
class Query {
  const DELETE = 1;
  const SAVE = 2;
  const FIND = 3;
  const FIND_ONE = 4;
  const COUNT = 5;

  const ERR_UNKNOWN_ACTION = 'Wrong query! Unknown action!';
  const ERR_CONDITIONS_NOT_CLOSED = 'Wrong query. Conditions are not closed.';
  /**
   *
   * @var class
   */
  private $class = null;

  /**
   *
   * @var string 
   */
  private $orderBy = null;

  /**
   *
   * @var boolean
   */
  private $ascending = true;

  /**
   *
   * @var array
   */
  private $conditions = array();

  /**
   *
   * @var int
   */
  private $action = null;

  /**
   *
   * @var int
   */
  private $lastIndex = 0;

  /**
   *
   * @var int
   */
  private $limit = null;

  /**
   *
   * @param class $class
   */
  public function __construct($class) {
    $this->setClass($class);
  }

  /**
   *
   * @param class $class
   * @return Query
   */
  public static function on($class) {
    return new Query($class);
  }

  /**
   * @return boolean
   */
  private function commonCheck() {
    if (! $this->areConditionsClosed() || $this->class === null || !class_exists($this->class)) {
      return false;
    } else {
      return true;
    }
  }

  /**
   * @return string
   */
  protected function compile() {
    $this->commonCheck();
    if ($this->action != null) {
      $query = '';
      switch ($this->action) {
        case Query::COUNT:
          $query .= 'SELECT COUNT(*) FROM ';
          break;
        case Query::DELETE:
          $query .= 'DELETE FROM ';
          break;
        case Query::FIND_ONE:
          $this->limit = 1;
        case Query::FIND:
          $query .= 'SELECT * FROM ';
          break;
        default :
          throw new QueryException(self::ERR_UNKNOWN_ACTION);
      }

      $class = $this->class;
      $fieldsMapping = PicoORM::getStatic($class, 'fieldsMapping');

      $query .= PicoORM::getStatic($class, 'tableName');

      if (count ($this->conditions) > 0) {
        $query .= ' WHERE ';
      }

      $conditionsStringArray = array();
      $parameterCount = 0;
      $parameters = array();

      foreach ($this->conditions as $condition) {
        $tableField = array_search($condition[0], $fieldsMapping);

        if ($tableField) {
          $condition[0] = $tableField;
        }

        $conditionsStringArray[]= $condition[0].' '.$condition[1].' '.':parameter'.$parameterCount;
        $parameters["parameter$parameterCount"] = $condition[2];
        $parameterCount++;
      }

      $query .= implode(' && ', $conditionsStringArray);

      $orderBy = $this->getOrderBy();
      $mapping = array_search($orderBy, $fieldsMapping);
      if ($mapping) {
        $orderBy = $mapping;
      }
      if ($orderBy) {
        $query .= " ORDER BY $orderBy ".($this->getAscending()?'ASC': 'DESC');
      }

      if ($this->limit) {
        $query .= ' LIMIT '.$this->limit;
      }
      return array( 
        'query'=> $query,
        'parameters' => $parameters
      );
    } else {
      throw new QueryException(self::ERR_UNKNOWN_ACTION);
    }
  }

  /**
   *
   * @return mixed
   */
  protected function execute() {
    $queryArr = $this->compile();
    $query = $queryArr['query'];
    $parameters = $queryArr['parameters'];

    $result = PicoORM::instance()->query($query, $this->class, $parameters);

    if ($result) {
      if ($this->limit == 1 && $this->action == self::FIND_ONE){
        $result[0]->setNew(false);
        $result[0]->setChanged(false);
        return $result[0];
      } else if ($this->action == self::COUNT){
        $fields = $result[0]->getFields();
        return (int) array_pop($fields);
      } else {
        foreach ($result as $key => $res) {
          $result[$key]->setNew(false);
          $result[$key]->setChanged(false);
        }
        return $result;
      }

    } else {
      return null;
    }
  }

  /**
   *
   * @return boolean
   */
  private function isConditionOpened() {
    if (count($this->conditions[count($this->conditions) - 1]) == 1) {
      return true;
    } else {
      return false;
    }
  }

  /**
   * @return boolean
   */
  protected function areConditionsClosed() {
    foreach ($this->getConditions() as $condition) {
      if (count($condition) <= 1) {

        return false;
      }
    }
    return true;
  }

  /**
   * @return int
   */
  public function count() {
    $this->setAction(self::COUNT);
    return $this->execute();
  }

  /**
   * @return boolean
   */
  public function delete() {
    $this->setAction(self::DELETE);
    return $this->execute();
  }

  /**
   *
   * @param mixed $value
   * @return Query
   */
  public function isEqual($value) {
    if ($this->isConditionOpened()) {
      $lastIndex = count($this->conditions) - 1;
      $this->conditions[$lastIndex][1] = '=';
      $this->conditions[$lastIndex][2] = $value;

      return $this;
    } else {
      throw new QueryException('Wrong query. Cannot compare value with no field.');
    }
  }

  /**
   * @return array
   */
  public function find() {
    $this->setAction(self::FIND);

    if ($this->commonCheck()) {
      return $this->execute();
    } else {
      $this->setAction(null);
      throw new QueryException('Wrong query. Conditions are not closed or class was not found.');
    }
  }

  public function limit($limit) {
    if ($this->commonCheck()) {
      $this->setLimit($limit);

      return $this;
    } else {
      throw new QueryException('Wrong query. Conditions are not closed or class was not found.');
    }
  }

  /**
   * @return Model
   */
  public function findOne() {
    $this->setAction(self::FIND_ONE);

    if ($this->commonCheck()) {
      return $this->execute();
    } else {
      $this->setAction(null);
      throw new QueryException('Wrong query. Conditions are not closed or class was not found.');
    }

  }

  /**
   *
   * @param mixed $value
   * @return Query
   */
  public function isGreaterEqual($value) {
    if ($this->isConditionOpened()) {
      $lastIndex = count($this->conditions) - 1;
      $this->conditions[$lastIndex][1] = '>=';
      $this->conditions[$lastIndex][2] = $value;

      return $this;
    } else {
      throw new QueryException('Wrong query. Cannot compare value with no field.');
    }

  }

  /**
   *
   * @param mixed $value
   * @return Query
   */
  public function isGreater($value) {
    if ($this->isConditionOpened()) {
      $lastIndex = count($this->conditions) - 1;
      $this->conditions[$lastIndex][1] = '>';
      $this->conditions[$lastIndex][2] = $value;

      return $this;
    } else {
      throw new QueryException('Wrong query. Cannot compare value with no field.');
    }


  }

  /**
   *
   * @param mixed $value
   * @return Query
   */
  public function isLesserEqual($value) {
    if ($this->isConditionOpened()) {
      $lastIndex = count($this->conditions) - 1;
      $this->conditions[$lastIndex][1] = '<=';
      $this->conditions[$lastIndex][2] = $value;

      return $this;
    } else {
      throw new QueryException('Wrong query. Cannot compare value with no field.');
    }
  }

  /**
   *
   * @param mixed $value
   * @return Query
   */
  public function isLesser($value) {
    if ($this->isConditionOpened()) {
      $lastIndex = count($this->conditions) - 1;
      $this->conditions[$lastIndex][1] = '<';
      $this->conditions[$lastIndex][2] = $value;

      return $this;
    } else {
      throw new QueryException('Wrong query. Cannot compare value with no field.');
    }
  }

  /**
   *
   * @param string $value
   * @return Query
   */
  public function isLike($value) {
    if ($this->isConditionOpened()) {
      $lastIndex = count($this->conditions) - 1;
      $this->conditions[$lastIndex][1] = 'LIKE';
      $this->conditions[$lastIndex][2] = $value;

      return $this;
    } else {
      throw new QueryException('Wrong query. Cannot compare value with no field.');
    }
  }

  /**
   *
   * @param mixed $value
   * @return Query
   */
  public function isNotEqual($value) {
    if ($this->isConditionOpened()) {
      $lastIndex = count($this->conditions) - 1;
      $this->conditions[$lastIndex][1] = '<>';
      $this->conditions[$lastIndex][2] = $value;

      return $this;
    } else {
      throw new QueryException('Wrong query. Cannot compare value with no field.');
    }
  }

  /**
   *
   * @param string $field
   * @return Query
   */
  public function where($field) {
    $this->conditions[] =array($field);
     return $this;
  }

  /**
   *
   * @param string $field
   * @param boolean $asc
   * @return Query
   */
  public function orderBy($field, $asc = true) {
    if ($this->commonCheck()) {
      $this->setOrderBy($field);
      $this->setAscending($asc);

      return $this;
    } else {
      throw new QueryException('Wrong query. Conditions are not closed or class was not found.');
    }
  }

  /**
   *
   * @return class
   */
  public function getClass() {
    return $this->class;
  }

  /**
   *
   * @param class $class
   */
  public function setClass($class) {
    $this->class = $class;
  }

  /**
   *
   * @return array
   */
  public function getConditions() {
    return $this->conditions;
  }

  /**
   *
   * @param array $conditions
   */
  public function setConditions($conditions) {
    $this->conditions = $conditions;
  }

  /**
   *
   * @return int
   */
  public function getAction() {
    return $this->action;
  }

  /**
   *
   * @param int $action
   */
  public function setAction($action) {
    $this->action = $action;
  }

  /**
   *
   * @return int
   */
  protected function getLastIndex() {
    return $this->lastIndex;
  }

  /**
   *
   * @param int $lastIndex
   */
  protected function setLastIndex($lastIndex) {
    $this->lastIndex = $lastIndex;
  }

  /**
   *
   * @return int
   */
  public function getLimit() {
    return $this->limit;
  }

  /**
   *
   * @param int $limit
   */
  public function setLimit($limit) {
    $this->limit = $limit;
  }

  /**
   *
   * @return string
   */
  public function getOrderBy() {
    return $this->orderBy;
  }

  /**
   *
   * @param string $orderBy
   */
  public function setOrderBy($orderBy) {
    $this->orderBy = $orderBy;
  }

  /**
   *
   * @return boolean
   */
  public function getAscending() {
    return $this->ascending;
  }

  /**
   *
   * @param boolean $ascending
   */
  public function setAscending($ascending) {
    $this->ascending = $ascending;
  }
}
?>